package com.codemes.happylist.modules.pdf.service;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.*;
import com.codemes.happylist.utils.DocumentUtil;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.html2pdf.attach.impl.OutlineHandler;
import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.font.FontProvider;
import com.itextpdf.styledxmlparser.jsoup.Jsoup;
import com.itextpdf.styledxmlparser.jsoup.helper.StringUtil;
import com.itextpdf.styledxmlparser.jsoup.nodes.Document;
import com.itextpdf.styledxmlparser.jsoup.nodes.Element;
import com.itextpdf.styledxmlparser.jsoup.nodes.Node;
import com.itextpdf.styledxmlparser.jsoup.select.Elements;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.io.*;
import java.lang.reflect.WildcardType;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

/**
 * @author nscoffee
 */
@Slf4j
public class HtmlToPdfUtil {

    private HtmlToPdfUtil(){}

    public static final String RESOURCES_DIR = "/tmp";

    public static final String TAG_BODY = "body";

    public static File htmlToPdf(String htmlFullUrl,String targetDir,String contentClass) throws IOException, InterruptedException {
        return htmlToPdf(htmlFullUrl,targetDir,contentClass,"");
    }

    public static File htmlToPdf(String htmlContent,File destFile){
        ConverterProperties converterProperties = new ConverterProperties();
        FontProvider dfp = new DefaultFontProvider(true, true, true);
        converterProperties.setFontProvider(dfp);
        OutlineHandler outlineHandler = OutlineHandler.createStandardHandler();
        converterProperties.setOutlineHandler(outlineHandler);
        converterProperties.setBaseUri("");
        try (OutputStream out = new FileOutputStream(destFile)){
            log.info("begin convert html to pdf target file>>{}",destFile.getPath());
            Thread.sleep(2000);
            // 暂停2秒，等待静态文件下载成功
            HtmlConverter.convertToPdf(htmlContent,out);
        }catch (Exception e){
            log.error("convert to pdf occur error ,error ={}",e);
        }
        return destFile;
    }

    public static void htmlToPdf(String htmlFullUrl, org.w3c.dom.Document w3cDocument, String selectedNodeId, File destFile, Integer pageWidth)
            throws IOException, InterruptedException {

        String decodeFullUrl = URLDecoder.decode(htmlFullUrl, StandardCharsets.UTF_8);
        URL url = new URL(decodeFullUrl);

        String htmlFile = RandomUtil.randomString(10);
        String parentBaseDir = RESOURCES_DIR +File.separator + IdUtil.randomUUID();
        File originHtmlFile = new File(parentBaseDir, htmlFile);
        FileUtil.mkParentDirs(originHtmlFile);

        // transform the DOM
        String fullDoc = DocumentUtil.toHtml(w3cDocument);

        log.info("begin download css/images/js by the html > ");
        // 获取所有的 link img script标签
        Document document = Jsoup.parse(fullDoc,"utf-8");
        // 删除多余的节点
        clearNodesById(selectedNodeId, document);

        Elements links = document.getElementsByTag("link");
        Elements imgTags = document.getElementsByTag("img");
        String staticFileDir = parentBaseDir+"/statics/";
        // 下载css文件
        links.forEach(el -> {
            File filepath = downloadStaticFile(url, el,"href",staticFileDir);
            if(null==filepath){
                return;
            }
            log.info("full file path = {}" , filepath.getPath());
            el.attr("href","file://"+filepath.getAbsolutePath());
        });
        // 转换逻辑需要下载图片，下载样式表等，除了需要转换的内容外，其上层dom结构应该保留
        imgTags.forEach(el -> {
            File filepath = downloadStaticFile(url, el,"src",staticFileDir);
            if(null==filepath){
                return;
            }
            log.info("full file path = {}" , filepath.getPath());
            el.attr("src","file://"+filepath.getAbsolutePath());
        });
        // 将处理后的html写入文件
        String html = document.outerHtml();
        FileUtil.writeString(html,originHtmlFile,StandardCharsets.UTF_8);

        ConverterProperties converterProperties = new ConverterProperties();
        FontProvider dfp = new DefaultFontProvider(true, true, true);
        converterProperties.setFontProvider(dfp);
        OutlineHandler outlineHandler = OutlineHandler.createStandardHandler();
        outlineHandler.setDestinationNamePrefix(document.title());
        converterProperties.setOutlineHandler(outlineHandler);
        converterProperties.setBaseUri("");

        try (InputStream in = new FileInputStream(originHtmlFile);PdfDocument pdfDocument = new PdfDocument(new PdfWriter(destFile))){
            log.info("begin convert html to pdf target file>>{}",destFile.getPath());
            Thread.sleep(1000);
            // 暂停2秒，等待静态文件下载成功
            if(null == pageWidth){
                pdfDocument.setDefaultPageSize(PageSize.A4);
            }else{
                pdfDocument.setDefaultPageSize(new PageSize(Float.parseFloat(pageWidth.toString()),PageSize.A4.getHeight()));
            }
            HtmlConverter.convertToPdf(in, pdfDocument, converterProperties);
        }catch (Exception e){
            log.error("convert to pdf occur error ,url ={}",htmlFullUrl);
            e.printStackTrace();
            throw e;
        }finally {
            FileUtil.del(parentBaseDir);
        }
    }

    public static File htmlToPdf(String htmlFullUrl,String targetDir,String contentClass,String targetFileName) throws IOException, InterruptedException {
        if(StringUtils.isEmpty(targetDir)){
            targetDir = RESOURCES_DIR;
        }
        String decodeFullUrl = URLDecoder.decode(htmlFullUrl,"utf-8");
        URL url = new URL(decodeFullUrl);

        String htmlFile = RandomUtil.randomString(10);
        String parentBaseDir = targetDir +File.separator + IdUtil.randomUUID();
        File originHtmlFile = new File(parentBaseDir, htmlFile);
        FileUtil.mkParentDirs(originHtmlFile);

        log.info("begin download html from url >>{}",url);

        // TODO 可能出现404，需要考虑如何处理
        boolean downloadSuccess = false;
        int tryDownloadCount = 3;
        while (!downloadSuccess){
            try {
                HttpUtil.downloadFile(decodeFullUrl,originHtmlFile,120*1000);
                downloadSuccess = true;
            } catch (Exception e) {
                e.printStackTrace();
                if(tryDownloadCount<0 || !(e instanceof  HttpException)){
                    throw e;
                }
                // 重试一次
                log.error("下载超时，准备重试>>>>>{},剩余重试次数>{}",decodeFullUrl,tryDownloadCount);
                Thread.sleep(5000);
                tryDownloadCount--;
            }
        }


        log.info("begin download css/images/js by the html > ");
        // 获取所有的 link img script标签
        Document document = Jsoup.parse(originHtmlFile,"utf-8");
        // 删除多余的节点
        clearNodesByClassName(contentClass, document);

        Elements links = document.getElementsByTag("link");
        Elements imgTags = document.getElementsByTag("img");
        String staticFileDir = parentBaseDir+"/statics/";
        // 下载css文件
        links.forEach(el -> {
            File filepath = downloadStaticFile(url, el,"href",staticFileDir);
            if(null==filepath){
                return;
            }
            log.info("full file path = {}" , filepath.getPath());
            el.attr("href","file://"+filepath.getAbsolutePath());
        });

        imgTags.forEach(el -> {
            File filepath = downloadStaticFile(url, el,"src",staticFileDir);
            if(null==filepath){
                return;
            }
            log.info("full file path = {}" , filepath.getPath());
            el.attr("src","file://"+filepath.getAbsolutePath());
        });
        // 将处理后的html写入文件
        String html = document.outerHtml();
        FileUtil.writeString(html,originHtmlFile,StandardCharsets.UTF_8);

        ConverterProperties converterProperties = new ConverterProperties();
        FontProvider dfp = new DefaultFontProvider(true, true, true);
        converterProperties.setFontProvider(dfp);
        OutlineHandler outlineHandler = OutlineHandler.createStandardHandler();
        outlineHandler.setDestinationNamePrefix(document.title());
        converterProperties.setOutlineHandler(outlineHandler);
        converterProperties.setBaseUri("");
        if(StringUtils.isEmpty(targetFileName)){
            targetFileName = RandomUtil.randomString(20)+".pdf";
        }
        File destFile = new File(targetDir,targetFileName);
        try (InputStream in = new FileInputStream(originHtmlFile); OutputStream out = new FileOutputStream(destFile)){
            log.info("begin convert html to pdf target file>>{}",destFile.getPath());
            Thread.sleep(2000);
            // 暂停2秒，等待静态文件下载成功
            HtmlConverter.convertToPdf(in, out, converterProperties);
        }catch (Exception e){
            log.error("convert to pdf occur error ,url ={}",htmlFullUrl);
            e.printStackTrace();
            throw e;
        }finally {
            FileUtil.del(parentBaseDir);
        }
        return destFile;
    }

    private static void clearNodesByClassName(String contentClass, Document document) {
        // 删除无用的相邻节点
        Element contentEl = null;
        if(!StringUtils.isEmpty(contentClass)){
            contentEl = document.getElementsByClass(contentClass).first();
        }
        clearNodes(contentEl,document);
    }

    private static void clearNodesById(String nodeId, Document document) {
        // 删除无用的相邻节点
        Element contentEl = null;
        if(!StringUtils.isEmpty(nodeId)){
            contentEl = document.getElementById(nodeId);
        }
        clearNodes(contentEl,document);
    }

    private static void clearNodes(Element contentEl,Document document){
        // 删除所有的脚本节点
        document.getElementsByTag("script").remove();
        document.getElementsByTag("link").select("link[rel!=\"stylesheet\"]").remove();

        if(contentEl==null){
            return;
        }
        Node parent = contentEl.parent();
        while (null!=parent&&!parent.nodeName().equals(TAG_BODY)){
            contentEl.siblingElements().remove();
            contentEl = (Element)parent;
            parent = parent.parent();
        }
    }

    private static File downloadStaticFile(URL url, Element el, String attr,String directory) {
        String link = el.attr(attr);
        if (StringUtil.isBlank(link)) {
            return null;
        }
        if( link.startsWith("http")
                ||link.startsWith("https")
                ||link.startsWith("tcp")){
            return null;
        }
        String baseUrl = getUrlBasePath(url);
        String baseSchema = url.getProtocol();
        String fullUrl;
        File staticFile;
        if(link.startsWith("//")){
            fullUrl = baseSchema+":"+link;
        }else if(link.startsWith("/")){
            fullUrl = baseUrl+link;
        }else{
            String path = url.getPath();
            int lastSep = path.lastIndexOf("/");
            String absHtmlPath = path.substring(0,lastSep);
            fullUrl = baseUrl+absHtmlPath+"/"+link;
        }
        staticFile = new File(directory,RandomUtil.randomString(10));

        FileUtil.mkParentDirs(staticFile);

        log.info("download static url >{}",fullUrl);
        log.info("download static path >{}",staticFile.getPath());
        HttpUtil.downloadFile(fullUrl,staticFile);
        return staticFile;
    }

    private static String getUrlBasePath(URL url) {
        if(url.getPort()>0){
            return url.getProtocol()+"://"+ url.getHost()+":"+ url.getPort();
        }
        return url.getProtocol()+"://"+ url.getHost();
    }

}
